/*
 * Copyright (c) 2009, Hamish Morgan. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the University of Sussex nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package locusts.common.net;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import locusts.common.World;
import locusts.common.entities.Actor;
import locusts.common.entities.CollidableEntity;
import locusts.common.entities.CropsEntity;
import locusts.common.entities.Entity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Hamish Morgan
 */
public abstract class WorldUpdateMessage implements Serializable {

    private transient static final Logger LOG = LoggerFactory.getLogger(
            WorldUpdateMessage.class);

    private WorldUpdateMessage() {
    }

    abstract public boolean isDelta();

    private static WorldUpdateMessage createFrom(World w) {
        return createFrom(w, false);
    }

    public static WorldUpdateMessage createFrom(World w, boolean forceFull) {
        WorldUpdateMessage out = null;
        if (forceFull || w.isModified()) {// || Math.random() < 0.01) {
            out = new Full(w);
            w.clearModified();
        } else
            out = new Delta(w);
        return out;
    }

    public abstract void applyTo(World other);

    /**
     *
     */
    private static class Full extends WorldUpdateMessage {

        private final World world;

        public Full(World world) {
            this.world = world.clone();
        }

        public void applyTo(final World other) {
            System.out.println("Full.appleTo");

            other.setEntities(world.getEntities());
            other.setTypes(world.getTypes());
            other.setWidth(world.getWidth());
            other.setHeight(world.getHeight());

            System.out.println("other.getState(): " + other.getState());
            System.out.println("world.getState(): " + world.getState());
            other.setState(world.getState());
        }

        public boolean isDelta() {
            return false;
        }
    }

    /**
     * 
     */
    private static class Delta extends WorldUpdateMessage {

        protected final List<Entity> changed;

        protected Delta(List<Entity> changed) {
            this.changed = changed;
        }

        public Delta(World world) {
            this(new ArrayList<Entity>());
            final List<Entity> ents = world.getEntities();
            for (Entity e : ents) {
                if (e.isModified()) {
                    changed.add(e);
                    e.clearModified();
                }
            }
        }

        public void applyTo(final World world) {
            final ListIterator<Entity> eIt = world.getEntities().
                    listIterator();
            final ListIterator<Entity> cIt = changed.listIterator();

            while (cIt.hasNext()) {
                Entity c = cIt.next();
                boolean found = false;
                while (!found && eIt.hasNext()) {
                    Entity e = eIt.next();
                    if (e.equals(c)) {
                        e.setPosition(c.getX(), c.getY());
                        e.setAlive(c.isAlive());
                        e.setEnergy(c.getEnergy());
                        e.setOrientation(c.getOrientation());
                        e.setTypeId(c.getTypeId());
                        if (e instanceof Actor && c instanceof Actor) {
                            Actor ae = (Actor) e;
                            Actor ac = (Actor) c;
                            ae.setVelocity(ac.getVX(), ac.getVY());
                            ae.setAcceleration(ac.getAX(), ac.getAY());
                        }
                        else if (e instanceof CropsEntity &&
                                c instanceof CropsEntity) {
                            ((CropsEntity) e).setPickup(((CropsEntity) c).
                                    isPickingUp());

                        }

//                        else if(e instanceof InputEntity) {
//                            ((InputEntity)e).setEvent(((InputEntity)c).getEvent());
//                        }

                        if(e instanceof CollidableEntity) {
                            ((CollidableEntity)e).setShape(((CollidableEntity)c).getShape());
                        }


//                        eIt.set(c);
                        found = true;
                    }
                }
                if (!found && LOG.isWarnEnabled())
                    LOG.warn("Failed to update Entity with id #{} when " +
                            "attempting to apply world delta.", c.getId());
            }

        }

        public boolean isDelta() {
            return true;
        }
    }
}


